/**
* Copyright @ 2023 iAuto (Shanghai) Co., Ltd.
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are NOT permitted except as agreed by
* iAuto (Shanghai) Co., Ltd.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
import { useMount, useUnmount } from 'ahooks';
import React, { useCallback, useEffect, useState } from 'react';

import VMScratchBlocks, { BLOCKS_DEFAULT_OPTIONS as blocksDefaultOptions, setPreviewMode } from '@/lib/blocks-vm';
import { getBlocks, getCategory, getSkillJson } from '@/lib/custom-extension';
import projectData from '@/lib/default-project/project-data';
import defineDynamicBlock from '@/lib/define-dynamic-block';
import makeToolboxXML from '@/lib/make-toolbox-xml';
import ArgumentTypes from '@scene/scene-vm/src/extension-support/argument-type';

// import { getAdminSceneDetail, getAdminSkillAll } from '@/services/blocks';
// import { getDetail } from '@/services/scenarioClass/audit';
import { checkSenceJson, replaceKeys } from '@/utils/utils';
import { message, Modal, Spin } from 'antd';

// import { getDeveloperRequest } from '@/services/developer';
// import { getOwnerRequest } from '@/services/owner';
import './index.less';
import { connect } from 'dva';
import template from '@/lib/default-project/template.json';
import { getBlocksDetail, getSkillList, sceneDetail } from '@/services/httpService';

const skillSet = new Set<string>();

interface KeysItem {
  id: string;
  name: string;
  blocks: any;
  menus: any;
  mSort: string;
}

type Keys = KeysItem[];

let workspace: any = null;
let ScratchBlocks: any = null;

type ISceneBlock = {
  fileUuid: string;
  seriesCode: string;
  modelCode: string;
  socVersion: string;
  dispatch?: any;
  onClose?: () => void;
  vm?: any;
};

const mapStateToProps = (state: any) => ({
  vm: state.vm,
});

const SceneBlock: React.FC<ISceneBlock> = ({ dispatch, vm, fileUuid, seriesCode, modelCode, socVersion, onClose }) => {
  const [loading, setLoading] = useState(false);
  const markBlockIds = new Map<string, number[]>();

  useEffect(() => {
    if (!vm) {
      dispatch({
        type: 'vm/init',
      });
    }
  }, [vm]);

  // 加载场景数据
  const loadProject = useCallback(
    (blocks = template) => {
      const markIds = (markBlockIds as Map<string, number[]>).get('markIds') || [];
      const checkRes = checkSenceJson(blocks, markIds);
      if (checkRes !== null) {
        Modal.warning({
          // content: modalMsg,
          content: <p>{`场景中存在部分车型无法使用的能力`}<br/>{`能力ID：${checkRes}`}</p>,
          okText: '确认',
          keyboard: false,
          onOk() {
            // onClose?.();
          },
        });
        return
      }
      // 加载 场景数据
      console.log("🚀 ~ file: index.tsx ~ line 65 ~ 加载 场景数据",)
      vm.loadProject(JSON.stringify(projectData(blocks)))
        .then(() => {
          //
        })
        .catch()
        .then(() => {
          Blockly.getMainWorkspace().scrollToLeftTop(-100, -100);
          if (workspace.topBlocks_[0]) {
            workspace.topBlocks_[0].svgGroup_.style.pointerEvents = 'none';
          }
        });
    },
    [vm],
  );

  // 获取扩展
  const getExtensions = (skillJson: any) => {
    const keys: Keys = [];
    const categorys = getCategory(skillJson);

    const categoryKeys = Object.keys(categorys);
    vm.runtime.attachCustomKey('categoryKeys', categoryKeys); // 存扩展keys
    categoryKeys.forEach((item) => {
      const { data, zhName, menuIconURI, menuActiveIconURI, mSort } = categorys[item];
      const { blocks, menus } = getBlocks(data, markBlockIds, false);
      // https://github.com/LLK/scratch-vm/blob/develop/docs/extensions.md
      const target = {
        blocks,
        menus,
        id: item,
        name: zhName,
        menuIconURI,
        menuActiveIconURI,
        mSort,
      };
      keys.push(target);
    });

    return keys;
  };

  const automaticImportComponent = async ({ id: key }: KeysItem) => {
    // 判断是否加载了扩展
    if (!vm.extensionManager.isExtensionLoaded(key)) {
      await vm.extensionManager.loadExtensionURL(key);
    }
  };

  // 加载扩展
  const loadExtensions = async (skillJson: any, projectJson: any) => {
    try {
      const extensions = getExtensions(skillJson);

      vm.runtime.attachCustomKey('abilityList', skillJson);

      // 注册扩展
      vm.extensionManager.addBuiltinExtensions(extensions);

      await Promise.all(extensions.map((item) => automaticImportComponent(item)));

      // 加载默认数据
      loadProject();
      // const res = checkSenceJson(projectJson);
      setLoading(false);
      // if (res.errStatus) {
      //   Modal.warning({
      //     title: '提示',
      //     content: `场景数据解析失败, 能力 ${res.errSkillds.join(',')} 不存在`,
      //   });
      //   return;
      // }
      loadProject(projectJson);
    } catch (error) {
      //
    }
  };

  const getToolboxXML = useCallback(() => {
    // Use try/catch because this requires digging pretty deep into the VM
    // Code inside intentionally ignores several error situations (no stage, etc.)
    // Because they would get caught by this try/catch
    try {
      let { editingTarget: target } = vm;
      const stage = vm.runtime.getTargetForStage();
      if (!target) target = stage; // If no editingTarget, use the stage

      const stageCostumes = stage.getCostumes();
      const targetCostumes = target.getCostumes();
      const targetSounds = target.getSounds();
      const dynamicBlocksXML = vm.runtime.getBlocksXML(target);
      return makeToolboxXML(
        false,
        target.isStage,
        target.id,
        dynamicBlocksXML,
        targetCostumes[targetCostumes.length - 1].name,
        stageCostumes[stageCostumes.length - 1].name,
        targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '',
      );
    } catch {
      return null;
    }
  }, [vm]);

  const onWorkspaceUpdate = useCallback(
    (data: { xml: string }) => {
      // When we change sprites, update the toolbox to have the new sprite's blocks
      const toolboxXML = getToolboxXML();
      workspace.updateToolbox(toolboxXML);
      const dom = ScratchBlocks.Xml.textToDom(data.xml);
      ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, workspace);
    },
    [workspace, ScratchBlocks, getToolboxXML],
  );

  // 扩展数据 回调处理 挂载到 ScratchBlocks.Blocks上
  const handleExtensionAdded = useCallback(
    (categoryInfo: any) => {
      const defineBlocks = (blockInfoArray: any) => {
        if (blockInfoArray && blockInfoArray.length > 0) {
          const staticBlocksJson: any[] = [];
          const dynamicBlocksInfo: any[] = [];
          blockInfoArray.forEach((blockInfo: any) => {
            if (blockInfo.info && blockInfo.info.isDynamic) {
              dynamicBlocksInfo.push(blockInfo);
            } else if (blockInfo.json) {
              staticBlocksJson.push(blockInfo.json);
            }
            // otherwise it's a non-block entry such as '---'
          });

          ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
          dynamicBlocksInfo.forEach((blockInfo) => {
            // This is creating the block factory / constructor -- NOT a specific instance of the block.
            // The factory should only know static info about the block: the category info and the opcode.
            // Anything else will be picked up from the XML attached to the block instance.
            const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
            const blockDefinition = defineDynamicBlock(
              ScratchBlocks,
              categoryInfo,
              blockInfo,
              extendedOpcode,
            );
            ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
          });
        }
      };

      // @iauto/scene-blocks implements a menu or custom field as a special kind of block ("shadow" block)
      // these actually define blocks and MUST run regardless of the UI state
      defineBlocks(
        Object.getOwnPropertyNames(categoryInfo.customFieldTypes).map(
          (fieldTypeName) => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition,
        ),
      );
      defineBlocks(categoryInfo.menus);
      defineBlocks(categoryInfo.blocks);

      try {
        // 限制大小
        const mathNumberLimits: any[] = [];
        // 自定义name
        const textLimits: any[] = [];

        categoryInfo.blocks.forEach((block) => {
          const extensionId = categoryInfo.id;
          block.json?.args0?.forEach((arg) => {
            const argInfo = block?.info?.arguments?.[arg.name];
            if (argInfo) {
              if (!argInfo.menu && argInfo.type === ArgumentTypes.STRING) {
                textLimits.push({
                  json: {
                    message0: '%1',
                    args0: [
                      {
                        type: 'field_input',
                        name: 'TEXT',
                        paramName: arg.name,
                      },
                    ],
                    output: 'String',
                    outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND,
                    colour: ScratchBlocks.Colours.textField,
                    colourSecondary: ScratchBlocks.Colours.textField,
                    colourTertiary: ScratchBlocks.Colours.textField,
                    type: `${extensionId}_text_${arg.name}`,
                  },
                });
              }

              if (!argInfo.menu && argInfo.type === ArgumentTypes.NUMBER) {
                mathNumberLimits.push({
                  json: {
                    message0: '%1',
                    args0: [
                      {
                        type: 'field_number',
                        name: 'NUM',
                        value: '0',
                        min: arg.min,
                        max: arg.max,
                        paramName: arg.name,
                      },
                    ],
                    output: 'Number',
                    outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND,
                    colour: ScratchBlocks.Colours.textField,
                    colourSecondary: ScratchBlocks.Colours.textField,
                    colourTertiary: ScratchBlocks.Colours.textField,
                    type: `${extensionId}_math_number_${arg.name}`,
                    limit: true,
                  },
                });
              }
            }
          });
        });

        defineBlocks(mathNumberLimits);
        defineBlocks(textLimits);
      } catch (error) {
        console.log('🚀 ~ file: index.tsx ~ line 336 ~ error', error);
      }
    },
    [ScratchBlocks],
  );

  const attachVM = useCallback(() => {
    workspace.addChangeListener(vm.blockListener);
    const flyoutWorkspace = workspace.getFlyout().getWorkspace();
    flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
    flyoutWorkspace.addChangeListener(vm.monitorBlockListener);

    vm.addListener('workspaceUpdate', onWorkspaceUpdate);
    vm.addListener('EXTENSION_ADDED', handleExtensionAdded);
  }, [vm, workspace, onWorkspaceUpdate, handleExtensionAdded]);

  const detachVM = useCallback(() => {
    vm.removeListener('workspaceUpdate', onWorkspaceUpdate);
    vm.removeListener('EXTENSION_ADDED', handleExtensionAdded);

    workspace?.dispose();
  }, [vm, workspace, onWorkspaceUpdate, handleExtensionAdded]);

  // 注册工作区
  const setupWorkspace = async () => {
    setPreviewMode(true);
    setLoading(true);
    Blockly.getMainWorkspace()?.clear();
    // clearScratchBlocks();
    if (!workspace) {
      ScratchBlocks = VMScratchBlocks(vm);

      const toolbox = makeToolboxXML(false, true, null, [], '', '', '');
      const workspaceConfig = {
        ...blocksDefaultOptions,
        toolbox: toolbox,
      };
      ScratchBlocks.ScratchMsgs.setLocale('zh-cn');
      workspace = ScratchBlocks.inject('preview', workspaceConfig);
      attachVM();
    }

    const skillParam: {
      modelCode: string;
      seriesCode: string;
      socVersion?: string;
    } = {
      seriesCode,
      modelCode,
    }

    if (socVersion !== "default") {
      skillParam.socVersion = socVersion
    }

    // 获取能力 获取详情
    const [skillRes, detailRes] = await Promise.all([
      getSkillList(skillParam),
      getBlocksDetail({ fileUuid, modelCode, seriesCode, socVersion }),
    ]);

    if (skillRes.statusCode !== "0") {
      message.warning("能力获取失败");
      return
    }

    if (detailRes.statusCode !== "0") {
      message.warning("场景获取失败");
      return
    }

    const reKeys = {
      inargs: 'inputArgs',
      id: 'ID',
      category: 'className',
      value: 'defaultValue',
    };
    const ignore: string[] = [];
    const json = replaceKeys(skillRes.data.skills, reKeys, ignore);
    const { skillJson } = getSkillJson(json, false);
    let projectJson = JSON.parse(detailRes.data.blocks);
    loadExtensions(skillJson, projectJson);
  };

  useEffect(() => {
    if (vm && fileUuid) {
      // 注册工作区
      setupWorkspace();
    }
  }, [vm && fileUuid])

  useUnmount(() => {
    detachVM();
    // clear vm
    dispatch({
      type: 'vm/clear',
    });
    workspace = null;
    ScratchBlocks = null;
    setPreviewMode(false);
  });

  return (
    <Spin spinning={loading}>
      <div id="preview" />
    </Spin>
  );
};

export default connect(mapStateToProps)(SceneBlock);
